/*
 * $Id: BoundingBox.java,v 1.2 2007/08/26 18:56:35 gil1 Exp $ $Date: 2007/08/26
 * 18:56:35 $ Copyright (c) Eric Z. Beard, ericzbeard@hotmail.com This library
 * is free software; you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version. This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package gnu.jpdf;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * <p>
 * This class simplifies the placement of Strings within a canvas area where the
 * placement of objects is absolute
 * </p>
 * <p>
 * A <code>BoundingBox</code> is just a Rectangle that knows how to find the
 * coordinates for a String based on the desired alignment and
 * <code>FontMetrics</code>. For each new String, a new child
 * <code>BoundingBox</code> is made that can be subtracted from the original box
 * so new Strings can be added
 * </p>
 * <p>
 * One of the more helpful features of this class is the string wrap feature of
 * <code>getStringBounds</code>. The box returned by that method will contain an
 * array of strings that have been broken down to fit the box. The box's
 * coordinates and size will reflect the size of the entire group of strings if
 * it is laid out as expected. Using the returned box and iterating through the
 * array of strings from top to bottom, getting new bounding boxes for each one
 * (with upper left alignment and no padding) will result in the correct string
 * wrap.
 * </p>
 * <p>
 * Note that you will need to have Xvfb running on a Unix server to use this
 * class
 * </p>
 * 
 * @author Eric Z. Beard, ericzbeard@hotmail.com
 * @version $Revision: 1.2 $, $Date: 2007/08/26 18:56:35 $
 */
public class BoundingBox extends Rectangle
{
    /** Percent f line height to space lines */
    public static final int LINE_SPACING_PERCENTAGE = 20;
    
    /** Used to a align a String centered vertically */
    public static final int VERT_ALIGN_CENTER = 0;
    
    /** Used to align a String at the top of the box */
    public static final int VERT_ALIGN_TOP = 1;
    
    /** Used to align a String at the bottom of the box */
    public static final int VERT_ALIGN_BOTTOM = 2;
    
    /** Used to align a String horizontally in the center of the box */
    public static final int HORIZ_ALIGN_CENTER = 3;
    
    /** Used to align a String to the left in the box */
    public static final int HORIZ_ALIGN_LEFT = 4;
    
    /** Used to aling a String to the right in a box */
    public static final int HORIZ_ALIGN_RIGHT = 5;
    
    /** Used to subtract a child from a box, *leaving* the top portion */
    public static final int SUBTRACT_FROM_TOP = 6;
    
    /** Used to subtract a child from a box, *leaving* the bottom portion */
    public static final int SUBTRACT_FROM_BOTTOM = 7;
    
    /** Used to subtract a child from a box, *leaving* the left portion */
    public static final int SUBTRACT_FROM_LEFT = 8;
    
    /** Used to subtract a child from a box, *leaving" the right portion */
    public static final int SUBTRACT_FROM_RIGHT = 9;
    
    private static final int[] VERT_ALIGNS = { VERT_ALIGN_CENTER,
                                            VERT_ALIGN_TOP,
                                            VERT_ALIGN_BOTTOM };
    
    private static final int[] HORIZ_ALIGNS = { HORIZ_ALIGN_CENTER,
                                             HORIZ_ALIGN_LEFT,
                                             HORIZ_ALIGN_RIGHT };
    
    private static final int[] SUBTRACTS = { SUBTRACT_FROM_TOP,
                                          SUBTRACT_FROM_BOTTOM,
                                          SUBTRACT_FROM_LEFT,
                                          SUBTRACT_FROM_RIGHT };
    
    /** The point to use for Graphics.drawString() */
    private Point drawingPoint;
    
    /** The absolute, world location of the box */
    private Point absoluteLocation;
    
    /** Link to parent box */
    private BoundingBox parent;
    
    /**
     * If this box was the result of a getStringBounds call, this array will
     * hold the broken strings
     */
    private String[] stringArray;
    
    /** The string specified in getStringBounds */
    private String fullString;
    
    /**
     * Creates a new <code>BoundingBox</code> instance.
     * 
     * @param p
     *            a <code>Point</code>, upper left coords
     * @param d
     *            a <code>Dimension</code>, used to determine height and width
     */
    public BoundingBox(Point p, Dimension d)
    {
        super(p, d);
        this.drawingPoint = this.getLocation();
        this.absoluteLocation = this.getLocation();
    }
    
    /**
     * <p>
     * Returns true if this box has a parent. The 'world', or enclosing canvas
     * is not considered a parent
     * </p>
     * 
     * @return a <code>boolean</code> value
     */
    public boolean hasParent()
    {
        return parent != null;
    }
    
    /**
     * <p>
     * Get this box's parent box
     * </p>
     * 
     * @return a <code>BoundingBox</code> value
     */
    public BoundingBox getParent()
    {
        return parent;
    }
    
    /**
     * <p>
     * Make the specified box this box's child. Equivalent to
     * <code>child.setParent(parent)</code> where the specified 'parent' is this
     * instance
     * </p>
     * 
     * @param child
     *            a <code>BoundingBox</code>, any box that can fit inside this
     *            one. The results of calling <code>getAbsoluteLocation()</code>
     *            on the child will be altered after this to take into account
     *            the child's new location in the 'world'
     */
    public void add(BoundingBox child)
    {
        child.setParent(this);
    }
    
    /**
     * <p>
     * Make the specified box this box's parent
     * </p>
     * 
     * @param parent
     *            a <code>BoundingBox</code> value
     */
    public void setParent(BoundingBox parent)
    {
        // Prevent infinite recursion
        if (this == parent)
        {
            return;
        }
        this.parent = parent;
        
        // If this box was created empty, without a String inside,
        // determine its absolute location
        if (this.getLocation().equals(this.getAbsoluteLocation()))
        {
            int ancestorTranslateX = 0;
            int ancestorTranslateY = 0;
            
            BoundingBox ancestor = this;
            while (ancestor.hasParent())
            {
                BoundingBox oldRef = ancestor;
                ancestor = ancestor.getParent();
                // Prevent infinite recursion
                if (ancestor == oldRef)
                {
                    break;
                }
                ancestorTranslateX += (int) ancestor.getLocation().getX();
                ancestorTranslateY += (int) ancestor.getLocation().getY();
            }
            
            this.getAbsoluteLocation().translate(ancestorTranslateX,
                                           ancestorTranslateY);
        } // end if
    } // end setParent
    
    /**
     * <p>
     * Get the wrapped strings if this box was from a call to getStringBounds,
     * otherwise this method returns null
     * </p>
     * 
     * @return a <code>String[]</code> array of strings, top to bottom in layout
     */
    public String[] getStringArray()
    {
        return stringArray;
    } // end getStringArray
    
    /**
     * <p>
     * Set the value of the string array
     * </p>
     * 
     * @param strArray
     *            a <code>String</code> array
     */
    public void setStringArray(String[] strArray)
    {
        this.stringArray = strArray;
    }
    
    /**
     * <p>
     * Set the absolute upper left world location point for this box
     * </p>
     * 
     * @param point
     *            a <code>Point</code> value
     */
    public void setAbsoluteLocation(Point point)
    {
        this.absoluteLocation = point;
    }
    
    /**
     * <p>
     * Returns false if for any reason this box has negative dimensions
     * </p>
     * 
     * @return true if the box has positive height and width, false otherwise.
     */
    public boolean boxExists()
    {
        return (this.getHeight() > 0 && this.getWidth() > 0);
    } // end boxExists
    
    /**
     * <p>
     * Get the absolute upper left location point for this box
     * </p>
     * 
     * @return a <code>Point</code> value
     */
    public Point getAbsoluteLocation()
    {
        return absoluteLocation;
    }
    
    /**
     * <p>
     * Returns the full string associated with a call to
     * <code>getStringBounds</code>
     * </p>
     * 
     * @return the full string.
     */
    public String getFullString()
    {
        return fullString;
    }
    
    /**
     * <p>
     * Sets the full string associated with <code>getStringBounds</code>
     * </p>
     * 
     * @param string
     *            a <code>String</code>
     */
    public void setFullString(String string)
    {
        this.fullString = string;
    }
    
    /**
     * <p>
     * Gets the location of a String after it is adjusted for alignment within
     * this box. The point's coordinates are either within this box or within
     * the enclosing area.
     * </p>
     * 
     * @param string
     *            a <code>String</code>, the String to be placed
     * @param hAlign
     *            an <code>int</code>, HORIZ_ALIGN_CENTER, HORIZ_ALIGN_LEFT,
     *            HORIX_ALIGN_RIGHT
     * @param vAlign
     *            an <code>int</code>, VERT_ALIGN_CENTER, VERT_ALIGN_TOP,
     *            VERT_ALIGN_BOTTOM
     * @param fm
     *            a <code>FontMetrics</code> object for this String
     * @param padding
     *            an <code>int</code>, the padding around the String
     * @param enforce
     *            a <code>boolean</code>, if true the method will throw an
     *            exception when the string is too big, if not true it will
     *            break the string down and overrun the bottom of the box. If
     *            the box is too small for even one word, the exception will be
     *            thrown
     * @return a <code>Point</code>, the coords to use in drawString()
     * @see #HORIZ_ALIGN_LEFT
     * @see #HORIZ_ALIGN_CENTER
     * @see #HORIZ_ALIGN_RIGHT
     * @see #VERT_ALIGN_TOP
     * @see #VERT_ALIGN_CENTER
     * @see #VERT_ALIGN_BOTTOM
     * @throws IllegalArgumentException
     *             if the args are invalid
     * @throws StringTooLongException
     *             if the string won't fit and enforce is set to true. The
     *             exception can still be thrown if enforce is false, but only
     *             in cases such as the box having no height or width
     */
    public BoundingBox getStringBounds(String string,
                                     int hAlign,
                                     int vAlign,
                                     FontMetrics fm,
                                     int padding,
                                     boolean enforce)
            throws IllegalArgumentException, StringTooLongException
    {
        // Check to make sure the values passed in are valid
        if (!checkHAlign(hAlign))
        {
            throw new IllegalArgumentException("BoundingBox.getStringBounds, " +
                        "hAlign invalid : " + hAlign);
        }
        if (!checkVAlign(vAlign))
        {
            throw new IllegalArgumentException("BoundingBox.getStringBounds, " +
                       "vAlign invalid : " + hAlign);
        }
        if (fm == null)
        {
            throw new IllegalArgumentException("BoundingBox.getStringBounds, " +
                    "FontMetrics null");
        }
        if (string == null)
        {
            throw new IllegalArgumentException("BoundingBox.getStringBounds, " +
                      "String null");
        }
        
        // NOTE: For this portion of the method, parent refers
        // to this object and child refers to the object about
        // to be created. When the absolute point for drawing the
        // String is determined, this object's ancestors are checked.
        Dimension parentSize = this.getSize();
        
        Point childLocation;
        Dimension childSize;
        
        // String ascent, width, height, parent, child width, height
        int sa, sw, sh, pw, ph, cw, ch;
        
        // Child, parent x, y coords for upper left
        int cx, cy, px, py;
        
        sa = fm.getMaxAscent();
        sw = fm.stringWidth(string);
        sh = sa + fm.getMaxDescent();
        pw = (int) parentSize.getWidth();
        ph = (int) parentSize.getHeight();
        if (pw < 0)
        {
            throw new StringTooLongException(
                    "The parent box has a negative width " +
                            " (" + pw + ")");
        }
        if (ph < 0)
        {
            throw new StringTooLongException(
                    "The parent box has a negative height" +
                            " (" + ph + ")");
        }
        cw = sw + padding * 2;
        ch = sh + padding * 2;
        px = (int) this.getX();
        py = (int) this.getY();
        
        String[] childStrArray = null;
        
        if ((cw > pw) || (string.indexOf("\n") != -1))
        {
            cw = pw - (padding * 2);
            childStrArray = createStringArray(string, fm, padding, pw);
            ch = getWrappedHeight(childStrArray, fm, padding);
            if (ch > ph)
            {
                // If enforce is not true, it means we want the box to
                // be returned anyway (along with the strings in the array)
                // so we can chop them manually and try again
                if (enforce)
                {
                    throw new StringTooLongException(
                            "The wrapped strings do not " +
                                    "fit into the parent box, pw=" + pw +
                                    ", ph=" + ph + ", ch=" + ch + ", cw=" + cw +
                                    ", string: " + string);
                }
            }
        }
        
        // Need to have child width and height, and string array set
        
        // Child location is relative to this (parent) box, not the world
        if (vAlign == VERT_ALIGN_TOP)
        {
            cy = 0;
        }
        else if (vAlign == VERT_ALIGN_CENTER)
        {
            cy = (ph / 2) - (ch / 2);
        }
        else
        {
            cy = ph - ch;
        }
        
        if (hAlign == HORIZ_ALIGN_LEFT)
        {
            cx = 0;
        }
        else if (hAlign == HORIZ_ALIGN_CENTER)
        {
            cx = (pw / 2) - (cw / 2);
        }
        else
        {
            cx = pw - cw;
        }
        
        childLocation = new Point(cx, cy);
        childSize = new Dimension(cw, ch);
        
        // Drawing location is based on the baseline of the String, and
        // relative to the world, not this box. The drawing point differs
        // from the absolute box location because of padding and ascent
        int dpx, dpy, abx, aby;
        
        // If this object also has a parent (maybe grandparents), iterate
        // through them and find the absolute 'world' location
        int ancestorTranslateX = 0;
        int ancestorTranslateY = 0;
        
        BoundingBox ancestor = this;
        while (ancestor.hasParent())
        {
            BoundingBox oldRef = ancestor;
            ancestor = ancestor.getParent();
            // Prevent infinite recursion
            if (ancestor == oldRef)
            {
                break;
            }
            ancestorTranslateX += (int) ancestor.getLocation().getX();
            ancestorTranslateY += (int) ancestor.getLocation().getY();
        }
        
        // Determine the absolute location for the box
        abx = px + cx + ancestorTranslateX;
        aby = py + cy + ancestorTranslateY;
        
        // Determine the absolute drawing point for the String
        dpx = abx + padding;
        dpy = aby + padding + sa;
        
        Point drawingPoint = new Point(dpx, dpy);
        BoundingBox returnChild = new BoundingBox(childLocation,
                                              childSize,
                                              drawingPoint,
                                              new Point(abx, aby));
        this.add(returnChild);
        returnChild.setFullString(string);
        returnChild.setStringArray(childStrArray);
        return returnChild;
        
    } // end getStringBounds
    
    /**
     * <p>
     * Gets the location of a String after it is adjusted for alignment within
     * this box. The point's coordinates are either within this box or within
     * the enclosing area.
     * </p>
     * <p>
     * By default, this method enforces string length and throws the exception
     * if it is too long
     * </p>
     * 
     * @param string
     *            a <code>String</code>, the String to be placed
     * @param hAlign
     *            an <code>int</code>, HORIZ_ALIGN_CENTER, HORIZ_ALIGN_LEFT,
     *            HORIX_ALIGN_RIGHT
     * @param vAlign
     *            an <code>int</code>, VERT_ALIGN_CENTER, VERT_ALIGN_TOP,
     *            VERT_ALIGN_BOTTOM
     * @param fm
     *            a <code>FontMetrics</code> object for this String
     * @param padding
     *            an <code>int</code>, the padding around the String
     * @return a <code>Point</code>, the coords to use in drawString()
     * @throws IllegalArgumentException
     *             if the args are invalid
     * @throws StringTooLongException
     *             if the string won't fit
     */
    public BoundingBox getStringBounds(String string,
                                     int hAlign,
                                     int vAlign,
                                     FontMetrics fm,
                                     int padding)
            throws StringTooLongException, IllegalArgumentException
    {
        return getStringBounds(string, hAlign, vAlign, fm, padding, true);
    } // end getStringBounds (enforce true by default)
    
    /**
     * <p>
     * This method is called after getting the box by calling
     * <code>getStringBounds</code> on the parent. Wraps the string at word
     * boundaries and draws it to the specified <code>Graphics</code> context.
     * Make sure padding is the same as specified for the
     * <code>getStringBounds</code> call, or you may get an unexpected
     * {@link gnu.jpdf.StringTooLongException}
     * </p>
     * 
     * @param g
     *            the <code>Graphics</code> object
     * @param fm
     *            the <code>FontMetrics</code> to use for sizing
     * @param padding
     *            an int, the padding around the strings
     * @param hAlign
     *            the <code>int</code> horizontal alignment
     * @throws IllegalArgumentException
     *             if the args are invalid
     * @throws StringTooLongException
     *             if the string won't fit this will only happen if the fm or
     *             padding has been changed since getStringBounds was called
     *             succesfully
     */
    public void drawWrappedString(Graphics g,
                                FontMetrics fm,
                                int padding,
                                int hAlign)
            throws IllegalArgumentException, StringTooLongException
    {
        if (getStringArray() == null)
        {
            Point p = getDrawingPoint();
            int xx = (int) p.getX();
            int yy = (int) p.getY();
            g.drawString(getFullString(), xx, yy);
        }
        else
        {
            int len = stringArray.length;
            for (int i = 0; i < len; i++)
            {
                BoundingBox wrappedBox = null;
                wrappedBox = getStringBounds(stringArray[i],
                                         hAlign,
                                         BoundingBox.VERT_ALIGN_TOP,
                                         fm,
                                         0);
                Point pp = wrappedBox.getDrawingPoint();
                int xx = (int) pp.getX();
                if (hAlign == BoundingBox.HORIZ_ALIGN_RIGHT)
                {
                    xx -= padding;
                }
                if (hAlign == BoundingBox.HORIZ_ALIGN_LEFT)
                {
                    xx += padding;
                }
                int yy = (int) pp.getY() + padding;
                g.drawString(stringArray[i], xx, yy);
                subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
            }
        }
    } // end drawWrappedString
    
    /**
     * <p>
     * Draws lines from the wrapped string until there is no more room and then
     * stops. If there is no string or the box is too small for anything to be
     * drawn, does nothing
     * </p>
     * 
     * @param g
     *            the <code>Graphics</code> object to draw to
     * @param fm
     *            the <code>FontMetrics</code> object to use for string sizing
     * @param padding
     *            the <code>int</code> amount of padding around the string
     * @param hAlign
     *            the <code>int</code> horizontal alignment
     */
    public void drawWrappedStringTruncate(Graphics g,
                                        FontMetrics fm,
                                        int padding,
                                        int hAlign)
    {
        
        if (getStringArray() == null)
        {
            Point p = getDrawingPoint();
            int xx = (int) p.getX();
            int yy = (int) p.getY();
            if (getFullString() != null)
            {
                g.drawString(getFullString(), xx, yy);
            }
            else
            {
                System.err.println("getStringArray and getFullString are null");
            }
        }
        else
        {
            int totalHeight = 0;
            int len = stringArray.length;
            for (int i = 0; i < len; i++)
            {
                BoundingBox wrappedBox = null;
                try
                {
                    wrappedBox = getStringBounds(stringArray[i],
                                         hAlign,
                                         BoundingBox.VERT_ALIGN_TOP,
                                         fm,
                                         0,
                                         false);
                    totalHeight += (int) wrappedBox.getHeight();
                    if (getParent() != null)
                    {
                        if (totalHeight > (int) (getParent().getHeight()))
                        {
                            return;
                        }
                    }
                }
                catch (StringTooLongException stle)
                {
                    stle.printStackTrace();
                    return;
                }
                wrappedBox.drawChoppedString(g, fm, padding, hAlign);
                subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
            }
        }
    } // end drawWrappedStringTruncate
    
    /**
     * <p>
     * Take the first line of the string (if it is wrapped, otherwise just take
     * the whole string) and chop the end of it off to make it fit in the box.
     * If the box is smaller than one letter, draw nothing
     * </p>
     * 
     * @param g
     *            the <code>Graphics</code> object to draw to
     * @param fm
     *            the <code>FontMetrics</code> object to use for string sizing
     * @param padding
     *            the <code>int</code> amount of padding around the string
     * @param hAlign
     *            the <code>int</code> horizontal alignment
     */
    public void drawChoppedString(Graphics g,
                                FontMetrics fm,
                                int padding,
                                int hAlign)
    {
        
        String string = "";
        if (getStringArray() != null)
        {
            string = new String(getStringArray()[0]);
        }
        else
        {
            string = new String(getFullString());
        }
        BoundingBox choppedBox = null;
        try
        {
            choppedBox = getStringBounds(string,
                                   hAlign,
                                   VERT_ALIGN_TOP,
                                   fm,
                                   padding);
            Point p = choppedBox.getDrawingPoint();
            int x = (int) p.getX();
            int y = (int) p.getY();
            g.drawString(string, x, y);
        }
        catch (StringTooLongException stle)
        {
            // Doesn't fit - start cutting from the end until it does
            StringBuffer buf = new StringBuffer().append(string);
            if (buf.length() == 0)
            {
                System.out
                        .println("BoundingBox.drawChoppedString, buf len 0 ??");
                // return;
                throw new RuntimeException();
            }
            buf.deleteCharAt(buf.length() - 1);
            while ((fm.stringWidth(buf.toString()) > (int) getWidth()) &&
                    (buf.length() > 0))
            {
                buf.deleteCharAt(buf.length() - 1);
            }
            
            try
            {
                choppedBox = getStringBounds(buf.toString(),
                                     hAlign,
                                     VERT_ALIGN_TOP,
                                     fm,
                                     padding);
                Point pp = choppedBox.getDrawingPoint();
                int xx = (int) pp.getX();
                int yy = (int) pp.getY();
                g.drawString(string, xx, yy);
            }
            catch (StringTooLongException sstle)
            {
                // Must be a really small box!
                sstle.printStackTrace();
            }
        }
    } // end drawChoppedString
    
    /**
     * <p>
     * Get the total height of the box needed to contain the strings in the
     * specified array
     * </p>
     */
    private int getWrappedHeight(String[] strings, FontMetrics fm, int padding)
    {
        int ma = fm.getMaxAscent();
        int md = fm.getMaxDescent();
        int sh = ma + md;
        int hPad = sh / LINE_SPACING_PERCENTAGE;
        sh += hPad;
        int total = sh * strings.length;
        
        return total + (padding * 2);
    } // end getWrappedHeight
    
    /**
     * <p>
     * Make a string array from a string, wrapped to fit the box
     * </p>
     * <p>
     * If the line width is too short, the array is just a tokenized version of
     * the string
     * </p>
     * 
     * @param string
     *            - the <code>String</code> to convert to an array
     * @param
     */
    private String[] createStringArray(String string,
                                     FontMetrics fm,
                                     int padding,
                                     int pw)
    {
        if (string == null)
        {
            System.err.println("Tried createStringArray with null String");
            return null;
        }
        if (fm == null)
        {
            System.err.println("Tried createStringArray with null FontMetrics");
        }
        
        int lw = pw - (padding * 2);
        
        Vector<String> returnVector = new Vector<String>();
        // Return delimiters as tokens
        StringTokenizer st = new StringTokenizer(string, " \t\n\r\f", true);
        StringBuffer tempBuffer = new StringBuffer();
        StringBuffer finalBuffer = new StringBuffer();
        
        while (st.hasMoreTokens())
        {
            // Get the next word and add a space after it
            String tempString = st.nextToken();
            tempBuffer.append(tempString);
            
            // If we haven't reached the width with our current
            // line, keep adding tokens. Also, check for hard returns
            if ((fm.stringWidth(tempBuffer.toString()) < lw)
                    &&
                    (tempBuffer.toString()
                            .charAt(tempBuffer.toString().length() - 1) != '\n')
                    &&
                    (tempBuffer.toString()
                            .charAt(tempBuffer.toString().length() - 1) != '\r'))
            {
                finalBuffer.append(tempString);
                continue;
            }
            returnVector.addElement(finalBuffer.toString());
            finalBuffer.delete(0, finalBuffer.length());
            tempBuffer.delete(0, tempBuffer.length());
            if ((tempString.charAt(0) != '\n') &&
                    (tempString.charAt(0) != '\r'))
            {
                tempBuffer.append(tempString);
                finalBuffer.append(tempString);
            }
            continue;
            
        } // end while
        returnVector.addElement(finalBuffer.toString());
        
        int len = returnVector.size();
        // Init the class member field stringArray
        String[] childStrArray = new String[len];
        for (int i = 0; i < len; i++)
        {
            String curStr = (String) returnVector.get(i);
            childStrArray[i] = curStr;
        }
        
        return childStrArray;
        
    } // end createStringArray
    
    /**
     * <p>
     * Removes the child box from this parent box. The child must have this
     * object as its parent or the method does nothing. The BoundingBox returned
     * will be cut by an area equal to the child area plus the horizontal or
     * vertical strip in which it sits, depending on the 'subtractFrom' value
     * passed in
     * </p>
     * 
     * @param child
     *            a <code>BoundingBox</code> value
     * @param subtractFrom
     *            an <code>int</code>, SUBTRACT_FROM_LEFT, SUBTRACT_FROM_RIGHT,
     *            SUBTRACT_FROM_TOP, SUBTRACT_FROM_BOTTOM
     * @return a <code>BoundingBox</code> value
     * @see #SUBTRACT_FROM_LEFT
     * @see #SUBTRACT_FROM_RIGHT
     * @see #SUBTRACT_FROM_TOP
     * @see #SUBTRACT_FROM_BOTTOM
     */
    public BoundingBox subtract(BoundingBox child, int subtractFrom)
    {
        // First, check to see if the params are valid
        if (child == null)
        {
            throw new IllegalArgumentException("BoundingBox.subtract, "
                    + "BoundingBox child is null");
        }
        if (!child.hasParent())
        {
            throw new IllegalArgumentException("BoundingBox.subtract, "
                    + "BoundingBox child has no parent");
        }
        if (!(child.getParent() == this))
        {
            throw new IllegalArgumentException("BoundingBox.subtract, "
                    + "this is not BoundingBox child's parent");
        }
        // Now that we know the child is this object's child, we continue
        // and check the subtractFrom param
        int len = SUBTRACTS.length;
        boolean valid = false;
        for (int i = 0; i < len; i++)
        {
            if (subtractFrom == SUBTRACTS[i])
            {
                valid = true;
            }
        }
        if (!valid)
        {
            throw new IllegalArgumentException("BoundingBox.subtract, "
                    + "subtractFrom invalid: " + subtractFrom);
        }
        
        // Now we know the child is valid, and if the subtractFrom
        // preference was invalid, we subtract from the bottom
        
        // The child should no longer be used, since the parent
        // reference will be invalid
        child.setParent(null);
        
        int cx = (int) child.getLocation().getX();
        int cy = (int) child.getLocation().getY();
        int cw = (int) child.getSize().getWidth();
        int ch = (int) child.getSize().getHeight();
        int px = (int) this.getLocation().getX();
        int py = (int) this.getLocation().getY();
        int pw = (int) this.getSize().getWidth();
        int ph = (int) this.getSize().getHeight();
        
        switch (subtractFrom)
        {
            case SUBTRACT_FROM_LEFT:
                // This will be useful for right-justified Strings in tables
                pw = cx;
                this.setSize(new Dimension(pw, ph));
                return this;
                
            case SUBTRACT_FROM_RIGHT:
                // This will be useful for left justified Strings in tables
                px = px + cw + cx;
                pw = pw - cw - cx;
                this.setLocation(new Point(px, py));
                this.setSize(new Dimension(pw, ph));
                return this;
                
            case SUBTRACT_FROM_BOTTOM:
                py = py + ch + cy;
                ph = ph - ch - cy;
                this.setLocation(new Point(px, py));
                this.setSize(new Dimension(pw, ph));
                return this;
                
            case SUBTRACT_FROM_TOP:
                ph = cy;
                this.setSize(new Dimension(pw, ph));
                return this;
                
            default: // Should never happen
                break;
        } // end switch
        return this;
    } // end subtract
    
    /**
     * <p>
     * Gets the drawing point to use in Graphics drawing methods. After getting
     * a new BoundingBox with getStringBounds(), calling this method will give
     * you an absolute point, accounting for alignment and padding, etc, from
     * which to start drawing the String
     * </p>
     * <p>
     * If getStringBounds was not called (this is a parent box), the upper left
     * coordinates will be returned (this.getLocation())
     * </p>
     * 
     * @return a <code>Point</code>
     */
    public Point getDrawingPoint()
    {
        return drawingPoint;
    }
    
    // main method is for testing /////////////////
    
    /**
     * For testing
     * 
     * @param args
     *            a <code>String[]</code> value
     */
    public static void main(String[] args)
    {
        Point upperLeft = new Point(5, 5);
        Dimension bounds = new Dimension(100, 100);
        BoundingBox parent = new BoundingBox(upperLeft, bounds);
        String string = "Hello World!";
        Font font = new Font("SansSerif", Font.PLAIN, 12);
        Frame frame = new Frame();
        frame.addNotify();
        try
        {
            Image image = frame.createImage(100, 100);
            if (image == null)
            {
                System.err.println("image is null");
            }
            Graphics graphics = image.getGraphics();
            FontMetrics fm = graphics.getFontMetrics(font);
            BoundingBox child = parent
                            .getStringBounds(string,
                                             BoundingBox.HORIZ_ALIGN_LEFT,
                                             BoundingBox.VERT_ALIGN_TOP,
                                             fm,
                                             5);
            System.out.println("Drawing Point: " +
                         child.getDrawingPoint().toString());
            System.out.println("Now testing subtract() method...");
            
            parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
            System.out.println("parent: " + parent.toString());
            child = new BoundingBox(new Point(90, 110), new Dimension(100, 100));
            parent.add(child);
            System.out.println("child: " + child.toString());
            System.out.println();
            System.out.println("subtracting the child from the parent");
            System.out.println("SUBTRACT_FROM_TOP: ");
            parent = parent.subtract(child, SUBTRACT_FROM_TOP);
            System.out.println("new parent: " + parent.toString());
            System.out.println();
            System.out.println("Resetting parent");
            parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
            parent.add(child);
            System.out.println("SUBTRACT_FROM_BOTTOM");
            parent.subtract(child, SUBTRACT_FROM_BOTTOM);
            System.out.println("new parent: " + parent.toString());
            System.out.println();
            System.out.println("Resetting parent");
            parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
            parent.add(child);
            System.out.println("SUBTRACT_FROM_LEFT");
            parent.subtract(child, SUBTRACT_FROM_LEFT);
            System.out.println("new parent: " + parent.toString());
            System.out.println();
            System.out.println("Resetting parent");
            parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
            parent.add(child);
            System.out.println("SUBTRACT_FROM_RIGHT");
            parent.subtract(child, SUBTRACT_FROM_RIGHT);
            System.out.println("new parent: " + parent.toString());
            System.out.println();
            
            System.exit(0);
        }
        catch (Exception e)
        {
            e.printStackTrace();
            System.exit(1);
        }
    }
    
    // Private methods /////////////////////////////
    
    /**
     * Creates a new <code>BoundingBox</code> instance.
     * 
     * @param p
     *            a <code>Point</code> value
     * @param d
     *            a <code>Dimension</code> value
     * @param drawingPoint
     *            a <code>Point</code> value
     */
    private BoundingBox(Point p,
                      Dimension d,
                      Point drawingPoint,
                      Point absolute)
    {
        super(p, d);
        this.drawingPoint = drawingPoint;
        this.absoluteLocation = absolute;
    }
    
    /**
     * <p>
     * Checks the horizontal alignment passed into a method to make sure it is
     * one of the valid values
     * </p>
     * 
     * @param hAlign
     *            an <code>int</code> value
     * @return a <code>boolean</code> value
     */
    private boolean checkHAlign(int hAlign)
    {
        int len = HORIZ_ALIGNS.length;
        for (int i = 0; i < len; i++)
        {
            if (hAlign == HORIZ_ALIGNS[i])
            {
                return true;
            }
        }
        return false;
    }
    
    /**
     * <p>
     * Checks the vertical alignment passed into a method to make sure it is one
     * of the valid values
     * </p>
     * 
     * @param vAlign
     *            an <code>int</code> value
     * @return a <code>boolean</code> value
     */
    private boolean checkVAlign(int vAlign)
    {
        int len = VERT_ALIGNS.length;
        for (int i = 0; i < len; i++)
        {
            if (vAlign == VERT_ALIGNS[i])
            {
                return true;
            }
        }
        return false;
    }
    
} // end class BoundingBox

